using System;
using System.Collections;
using System.Collections.Generic;
using System.Html;
using System.Runtime.CompilerServices;
using System.Serialization;
using Awaiter;
using Nsb;
using Nsb.Classes;
using ThemeChain;
using WhirliGigEdit.Classes;
using Yoshi.Classes;
using jQueryApi;

// ReSharper disable SuspiciousTypeConversion.Global

namespace WhirliGig.Classes
{
    [IgnoreNamespace]
    public partial class GigShowEdit : GigShow, IGigShowEdit
    {
        public static GigShowEdit GseInstance;
        private int _bottom;
        private GigEdit _focused;
        private string _lastGroupId;
        private string _paintedId;

        public const string AfterDragName = "DoAfterDrag";
        public const string MaskClassName = "MaskClass";

        public GigShowEdit()
        {
            GseInstance = this;
            TestInstance.AddInstance(this);

            _dragOptions = new Dictionary(
                    "start", (Action<jQueryEvent, jQueryUiObject>) DragStart,
                    "drag", (Action<jQueryEvent, jQueryUiObject>) DragDrag,
                    "stop", (Action<jQueryEvent, jQueryUiObject>) DragStop
                    //"helper", "clone",
                    //"appendTo", PageSelectToEl
                    );

            new Await().LoadScript(Uri.ScriptsNd("Scripts/ParseXmlLib.js")).Commit();
            new Await().LoadJson("insertables", Uri.Scripts("Data/insertables.txt")).Commit();

            jQuery.Document.Bind("SaveSpinnerOn", SaveSpinnerOnEv);
            jQuery.Document.Bind("SaveSpinnerOff", SaveSpinnerOffEv);
            Listen();
        }

        #region IGigShowEdit Members

        public Gig NewGigEdit(GigShow gse)
        {
            return new GigEdit((GigShowEdit) gse);
        }

        public Dictionary Configure(GigData gd, Gig igg)
        {
            GigEdit gg = (GigEdit) igg;

            if (!gd.noCover)
            {
                gg.AddCover();
                int dl = -2 - Css.BorderWidth(gd.css, 0);
                Dictionary adj = new Dictionary(
                    "top", dl,
                    "left", dl,
                    "right", dl,
                    "bottom", dl
                    );
                gg.Cover.CSS(adj);
                gg.CoverInner.CSS(adj);
                if (gd.type == GigEdit.EmbeddedCodeType)
                {
                    gg.Cover.CSS(new Dictionary("top", dl - 22));
                    gg.CoverInner.CSS(new Dictionary("top", dl - 22));
                }
            }
            if (!String.IsNullOrEmpty(gd.groupId))
            {
                gg.Element.AddClass("Group GID" + gd.groupId);
            }
            else
            {
                gg.Element.RemoveClass("Group GID" + gd.groupId);
            }
            Dictionary css = (Dictionary) jQuery.Extend(new Dictionary(), gd.css);
            Dictionary cssI = new Dictionary();
            cssI["opacity"] = css["opacity"];
            css.Remove("opacity");
            gg.Content.CSS(cssI);
            return css;
        }

        [AlternateSignature]
        public extern void SaveAllAw(Await awp);

        public void UndoStart(string content, int slide)
        {
            Undo.Start(new UndoItem(content, slide), PartitionKey);
        }

        public void PageMenu()
        {
            ClearPaint();
            if (SlideControls == null)
                return;

            new Await()
                .AddDx(
                    delegate
                    {
                        SlideControls.Empty();

                        jQueryObject el = jQuery.FromHtml("<div class='SlideSelect'/>")
                            .AppendTo(SlideControls)
                            ;

                        //if (Editing)
                        //{
                        //    //jQuery.FromHtml("<span class='AddSlide'/>")
                        //    //    .AppendTo(el)
                        //    //    .Html("+")
                        //    //    .Attribute("title", WhilrliGigTheme.AddSlideTitle)
                        //    //    .Plugin<Jewl>().SwallowClick(AddSlideEv)
                        //    //    ;

                        //    //jQuery.FromHtml("<span class='RemSlide'/>")
                        //    //    .AppendTo(el)
                        //    //    .Html("-")
                        //    //    .Attribute("title", WhilrliGigTheme.RemSlideTitle)
                        //    //    .Plugin<Jewl>().SwallowClick(RemSlideEv)
                        //    //    ;
                        //}

                        if (_hasSelect)
                        {
                            jQueryObject sel = jQuery.FromHtml("<select/>")
                                .AppendTo(el)
                                .Change(ChangeSlideEv)
                                ;

                            foreach (SlideData sd in Slides)
                            {
                                jQuery.FromHtml("<option/>")
                                    .AppendTo(sel)
                                    .Attribute("value", sd.slide.ToString())
                                    .Html("slide " + (sd.slide + 1))
                                    ;
                            }

                            sel.Value(Slide.ToString());

                            if (Slides.Count > 1)
                            {
                                jQuery.FromHtml("<span class='PrevSlide'/>")
                                    .AppendTo(el)
                                    .Html("<")
                                    .Attribute("title", ThemeWhirliGig.PrevSlideTitle)
                                    .Plugin<Jewl>().SwallowClick(PrevSlideEv)
                                    ;

                                jQuery.FromHtml("<span class='NextSlide'/>")
                                    .AppendTo(el)
                                    .Html(">")
                                    .Attribute("title", ThemeWhirliGig.NextSlideTitle)
                                    .Plugin<Jewl>().SwallowClick(NextSlideEv)
                                    ;
                            }
                        }

                        if (Curating)
                        {
                            jQueryObject gearsOuter = jQuery.FromHtml("<div id='GearsBtnOuter' class='GearsBtnOuter'/>")
                                .AppendTo(el)
                                ;

                            jQueryObject gear = jQuery.FromHtml("<img id='GearsBtn' class='GearsBtn'/>")
                                .Attribute("src", Uri.Img("Common/gear16.gif"))
                                .AppendTo(gearsOuter)
                                ;

                            DDict mu = new DDict();

                            if (_playTmr == 0)
                            {
                                if (Storage.GetLocal("CopyBuffer") != null)
                                    mu.Add("&#9679; Paste to this page", PasteEv);
                                if (Undo.HasPrior(PartitionKey))
                                    mu.Add("&#9679; <b>&#8656; Undo</b> - <small>(ctrl-z)</small>", UndoEv);
                                if (Undo.HasNext(PartitionKey))
                                    mu.Add("&#9679; <b>Redo &#8658;</b> - <small>(ctrl-alt-z)</small>", RedoEv);

                                if ((bool) _opts["NoSlides"] != true)
                                {
                                    mu
                                        .Add("Play slide show", PlaySlidesEv)
                                        .Add("Copy this slide", CopySlideEv)
                                        .Add("<span style='font-family:Courier New'>+</span> Add new slide", AddSlideEv)
                                        .Add("<span style='font-family:Courier New'>-</span> Remove this slide", RemSlideEv)
                                        //.Add("Chomp Window", ChompWindowEv)
                                        ;
                                }
                                mu
                                    .Add("addablesBar", "<b>Insert</b>")
                                    .Add("&#9679; Text", delegate(jQueryEvent e, string item) { TextItem(); })
                                    .Add("&#9679; Numbered list", delegate(jQueryEvent e, string item) { HtmlOrderedList(); })
                                    .Add("&#9679; Button", delegate(jQueryEvent e, string item) { InsertButton(); })
                                    .Add("&#9679; Single-line form box", delegate(jQueryEvent e, string item) { InsertTextBox(); })
                                    .Add("&#9679; Multi-line form box", delegate(jQueryEvent e, string item) { InsertTextareaBox(); })
                                    //.Add("&#9679; Password form box", delegate(jQueryEvent e, string item) { InsertPasswordBox(); })
                                    .Add("&#9679; Page links box", delegate(jQueryEvent e, string item) { InsertLinks(); })
                                    .Add("&#9679; Embedded html code", delegate(jQueryEvent e, string item) { AskEmbeddedCode(); })
                                    /*
                                        .Add("&#9679; YouTube Video", "")
                                        .Add("&#9679; Ur Ten", "")
                                        .Add("&#9679; Rampage", "")
                                        .Add("&#9679; Focus Wheel", "")
                                        .Add("&#9679; Aliveness Feed", "")
                                         */
                                    .Add("commandsBar", "<b></b>")
                                    .Add("&#9679; Add images", UploadImgEv)
                                    .Add("&#9679; Admin page", delegate(jQueryEvent e) { Script.Literal("Master.GoPage({0},{1})", ThemeYoshi.DefaultPageClass, ThemeYoshi.AdminPageName); })
                                    .Add("&#9679; Manage pages", PagesEv)
                                    .Add("&#9679; Copy whole page", CopySlideEv)
                                    .Add("&#9679; Change page colors", delegate(jQueryEvent e, string item) { ColorizeBg(); })
                                    .Add("&#9679; Change page background", delegate(jQueryEvent e, string item) { AddNewBg(); })
                                    .Add("publishBar", "<b></b>")
                                    //.Add("&#9679; Publish this page", Publish.PublishThisPageEv)
                                    .Add("&#9679; Go to published page", ThemeYoshi.ToPublished)
                                    .Add("&#9679; Turn curating off", CuratingOff)
                                    ;
                            }
                            else
                                mu.Add("Stop slide show", StopSlidesEv);

                            new PopMenu()
                                .AddList(mu.End())
                                .WrapAround(gear)
                                .AddClass("EditGears")
                                ;
                        }
                    })
                .Commit();
        }

        public void AutoSize(GigData gd)
        {
            if (gd.autosize)
            {
                jQueryObject el = jQuery.Select("#" + gd.id);
                if (el.Length > 0)
                {
                    int w = el.GetWidth();
                    el.Width(w - (jQuery.Browser.MSIE ? 0 : 2)); // ie appears to have a narrower wrap width so must kludge the other browsers by reducing their width by 2
                    int carton = el.GetOuterHeight() - el.GetHeight() - (Number.ParseInt(el.GetCSS("border-left-width") ?? "0")*2);
                    gd.dim.height = el.Height("")[0].ScrollHeight - carton;
                    el.Height(gd.dim.height);
                    el.Width(w);
                    //Inform.Debug("AutoSize carton={0}, border-width={1}", carton, (Number.ParseInt(el.GetCSS("border-left-width") ?? "0") * 2));
                }
            }
        }

        #endregion

        public void SaveAllAw(Await awp, bool noUndo)
        {
            new Await()
                .AddDx(
                    delegate
                    {
                        int cnt = 0;
                        foreach (SlideData slide in Slides)
                        {
                            slide.slide = cnt++;
                        }
                        string content = Serialize();
                        if (content != LastSaved)
                        {
                            if (!noUndo)
                            {
                                Undo.Save(new UndoItem(content, Slide), PartitionKey);
                                RedrawPageGear();
                            }
                            LastSaved = content;
                            StoreBlobStringAw(awp, new Dictionary("partitionKey", PartitionKey, "rowKey", RowKey, "content", content, "backup", true));
                            DomElement.Trigger("OnPageSave", CurrentSlide);
                        }
                        else
                        {
                            awp.Done();
                        }
                        //Inform.Debug("Storing Gigs {0}", Serialize());
                    }
                )
                .AddFn(SetMinPageHeight)
                .Commit();
        }

        public void ReplaceSlides(List<SlideData> slides, int slide, bool noUndo)
        {
            if (Script.IsValue(slides))
            {
                Slide = slide;
                Slides = slides;
                new Await()
                    .AddAw(RestoreSlideAw, Slide)
                    .AddFn(RedrawPageGear)
                    .AddAw(SaveAllAw, noUndo)
                    .Commit();
            }
        }

        public void UndoEv(jQueryEvent e)
        {
            UndoItem ui = (UndoItem) Undo.Prior(PartitionKey);
            if (ui != null) ReplaceSlides(UndoItem.Parse(ui.Buffer), ui.Slide, true);
        }

        public void RedoEv(jQueryEvent e)
        {
            UndoItem ui = (UndoItem) Undo.Next(PartitionKey);
            if (ui != null) ReplaceSlides(UndoItem.Parse(ui.Buffer), ui.Slide, true);
        }

        public override void Destroy()
        {
            Muffel();
            base.Destroy();
        }

        public void PagesEv(jQueryEvent e)
        {
            Cluster.GlassOff();
            Pages.Ev(e);
        }

        public static void UploadImgEv(jQueryEvent e)
        {
            new Await()
                .LoadAndDo(Uri.Scripts("Scripts/PhotoUpload.js"), "PhotoUploadViewer.Ev", new Dictionary("Path", "pg/images"), e)
                .Commit();
        }

        public List<GroupItem> CollectGroup(jQueryEvent ev, string gid)
        {
            List<GroupItem> ls = new List<GroupItem>();

            if (!String.IsNullOrEmpty(gid))
                jQuery.Select(".GID" + gid, PageSelectToEl).Each(
                    delegate(int i, Element domEl)
                    {
                        GroupItem gi = new GroupItem();
                        gi.El = jQuery.FromElement(domEl);
                        if (gi.El.Length > 0)
                        {
                            gi.Gd = GigById(gi.El[0].ID);
                            gi.Pos = (Pos) jQuery.Extend(new Dictionary(), gi.Gd.pos);
                            // GigDataOffFix
                            //gi.Off = (Pos) jQuery.Extend(new Dictionary(), gi.Gd.off);
                            gi.e = ev;
                            ls.Add(gi);
                        }
                    });
            return ls;
        }

        //public List<GroupItem> CollectGroup2(jQueryEvent ev, string gid)
        //{
        //    Inform.MarkSt();
        //    List<GroupItem> ls = new List<GroupItem>();

        //    if (!String.IsNullOrEmpty(gid))
        //        foreach (KeyValuePair<string, GigData> p in CurrentSlide.gigs)
        //        {
        //            if (p.Value.groupId == gid)
        //            {
        //                GroupItem gg = new GroupItem();
        //                gg.El = jQuery.Select("#" + p.Value.id);
        //                if (gg.El.Length > 0)
        //                {
        //                    gg.Gd = p.Value;
        //                    gg.Pos = (Pos)jQuery.Extend(new Dictionary(), p.Value.pos);
        //                    gg.Off = (Pos)jQuery.Extend(new Dictionary(), p.Value.off);
        //                    gg.e = ev;
        //                    ls.Add(gg);
        //                }
        //            }
        //        }
        //    Inform.Mark();
        //    return ls;
        //}

        public void ForCluster(jQueryEvent e, Action<GroupItem> fn)
        {
            GigData gd = FocusedGd;
            _group = e.ShiftKey || gd.clusterId == gd.id ? CollectGroup(e, gd.groupId) : new List<GroupItem>();
            ForGroupSub(e, gd, fn);
        }

        public void ForGroup(jQueryEvent e, Action<GroupItem> fn)
        {
            GigData gd = FocusedGd;
            _group = e.ShiftKey ? CollectGroup(e, gd.groupId) : new List<GroupItem>();
            ForGroupSub(e, gd, fn);
        }

        public void ForGroupById(jQueryEvent e, string id, Action<GroupItem> fn)
        {
            GigData gd = GigById(id);
            _group = e.ShiftKey ? CollectGroup(e, gd.groupId) : new List<GroupItem>();
            ForGroupSub(e, gd, fn);
        }

        private void ForGroupSub(jQueryEvent e, GigData gd, Action<GroupItem> fn)
        {
            if (_group.Count == 0)
                _group.Add(new GroupItem().FillFromGd(e, gd));

            foreach (GroupItem gi in _group)
            {
                fn(gi);
            }
        }

        public static void GroupIt(string id, string gid)
        {
            GigData gd = Instance.GigById(id);
            gd.groupId = gid;
            jQueryObject el = GigElById(id);
            el.AddClass("Group GID" + gid);
            GigEdit ge = GigEditByEl(el);
            ge.MaskOff();
            ge.MaskOn();
        }

        [AlternateSignature]
        public extern void UngroupIt(string id);

        public void UngroupIt(string id, bool all)
        {
            GigData gd = GigById(id);
            if (!String.IsNullOrEmpty(gd.clusterId))
            {
                Ask.Ok(ThemeYoshi.UnGroupClusterMsg);
                return;
            }
            GigEditById(id).MaskOff();

            if (!String.IsNullOrEmpty(gd.groupId))
            {
                _group = CollectGroup(null, gd.groupId);
                if (all || _group.Count <= 2)
                    foreach (GroupItem gi in _group)
                    {
                        gi.Gd.groupId = null;
                        gi.El.RemoveClass("Group GID" + gi.Gd.groupId);
                    }
                else
                {
                    GigElById(gd.id).RemoveClass("Group GID" + gd.groupId);
                    gd.groupId = null;
                }
            }
            ClearPaint();
        }

        public void ClearPaint()
        {
            _paintedId = null;
        }

        public void Grouper(GigEdit gg, bool addIt)
        {
            if (addIt)
            {
                if (String.IsNullOrEmpty(FocusedGd.groupId) || _paintedId == null)
                {
                    if (_paintedId == null || _paintedId == FocusedGd.id)
                    {
                        _paintedId = FocusedGd.id;
                        return;
                    }
                    _lastGroupId = GigById(_paintedId).groupId ?? Formulas.NewId;
                    GroupIt(_paintedId, _lastGroupId);
                }
                else
                    _lastGroupId = FocusedGd.groupId;
                GroupIt(gg.Id, _lastGroupId);
                _paintedId = gg.Id;
            }
            else
            {
                UngroupIt(gg.Id);
            }
        }

        public void Groupify(GigData gd, bool ify)
        {
            _lastGroupId = Formulas.NewId;
            if (ify)
            {
                foreach (KeyValuePair<string, GigData> p in CurrentSlide.gigs)
                {
                    if (Collision(p.Value, gd))
                    {
                        //p.Value.groupId = _lastGroupId;
                        GroupIt(p.Value.id, _lastGroupId);
                    }
                }
                //foreach (GroupItem gg in CollectGroup(null, gd.groupId))
                //{
                //    GroupIt(gg.Gd.id, _lastGroupId);
                //}
            }
            else
            {
                UngroupIt(gd.id, true);
            }
            ClearPaint();
        }

        /// <summary>
        /// Any part of g1 overlapps g2.
        /// </summary>
        /// <param name="g1">GigData</param>
        /// <param name="g2">GigData</param>
        /// <returns>true if they overlap</returns>
        private static bool Collision(GigData g1, GigData g2)
        {
            int bw = Css.BorderWidth(g1.css);
            int ph1 = Css.PaddingHorz(g1.css) + bw;
            if (g1.pos.Left + g1.dim.width + ph1 < g2.pos.Left - bw) return false;
            if (g1.pos.Left - bw > g2.pos.Left + g2.dim.width + ph1) return false;
            int ph2 = Css.PaddingVert(g2.css) + bw;
            if (g1.pos.Top - bw > g2.pos.Top + g2.dim.height + ph2) return false;
            if (g1.pos.Top + g1.dim.height + ph2 < g2.pos.Top - bw) return false;
            return true;
        }

        public static void LinkifyContent(GigData gd, string url)
        {
            jQueryObject dl = GigEdit.Domify(gd.html);
            jQueryObject el = GigEdit.DomContent(dl);
            if (el.Parent().Is("a"))
                el.Unwrap();
            if (!String.IsNullOrEmpty(url))
                el.Wrap("<a href='" + ThemeYoshi.SmartUrl(url) + "'/>");
            gd.html = DomElement.OuterHtml(dl);
            dl.Remove();
        }

        public static string LinkHref(GigData gd)
        {
            jQueryObject dl = GigEdit.Domify(gd.html);
            string href = ThemeYoshi.UnSmartUrl(dl.Find("a").GetAttribute("href") ?? "");
            dl.Remove();
            return href;
        }

        private string Serialize()
        {
            //Script.Literal("debugger");
            Resort();
            string s = Json.Stringify(Slides, delegate(string n, object o) { return o; }, 4);
            return s;
        }

        private void Resort()
        {
            // Resort Gigs into actual document order
            Dictionary<string, GigData> gs = new Dictionary<string, GigData>();
            PageSelectToEl.Find(Gig.DotGigClassName)
                .Each(
                    delegate(int i, Element domEl)
                    {
                        jQueryObject el = jQuery.FromElement(domEl);
                        string id = el.GetAttribute("id");
                        gs[id] = Slides[Slide].gigs[id];
                    });
            CurrentSlide.gigs = gs;
        }

        private void Reorder()
        {
            // Resort embedded gigs into document position order
            List<GigData> g1 = new List<GigData>();
            List<GigData> g2 = new List<GigData>();
            foreach (KeyValuePair<string, GigData> gig in CurrentSlide.gigs)
            {
                if (gig.Value.embedded)
                    g1.Add(gig.Value);
                else
                {
                    g2.Add(gig.Value);
                }
            }

            g1.Sort(SortTops);

            jQueryObject pg = PageSelectToEl;

            Dictionary<string, GigData> gs = new Dictionary<string, GigData>();
            foreach (GigData gd in g2)
            {
                gs[gd.id] = gd;
                jQuery.Select("#" + gd.id).AppendTo(pg);
            }
            foreach (GigData gd in g1)
            {
                gs[gd.id] = gd;
                jQuery.Select("#" + gd.id).AppendTo(pg);
            }
            CurrentSlide.gigs = gs;
        }

        public void ReEmbedEv(jQueryEvent e)
        {
            _bottom = 0;
            Dictionary<string, bool> grps = new Dictionary<string, bool>(); // Keep track of completed groups so we don't do them again
            foreach (KeyValuePair<string, GigData> gig in CurrentSlide.gigs)
            {
                GigData gd = gig.Value;
                if (!gd.embedded)
                    continue;
                if (!String.IsNullOrEmpty(gd.groupId))
                {
                    if (!Type.HasField(grps, gd.groupId))
                    {
                        grps[gd.groupId] = true;
                        ReEmbedGrp(gd.groupId);
                    }
                    continue;
                }
                AutoSize(gd);
                if (_bottom != 0)
                    ReEmbed(gd, _bottom);
                // GigDataOffFix
                _bottom = GigElById(gd.id).GetOffset().Top + gd.dim.height + Css.OffsetVert(gd.css, Gig.DeefCss["padding-top"]);
            }
        }

        private void ReEmbedGrp(string grp)
        {
            _bottom = 0;
            foreach (KeyValuePair<string, GigData> gig in CurrentSlide.gigs)
            {
                GigData gd = gig.Value;
                if (!gd.embedded || gd.groupId != grp)
                    continue;
                AutoSize(gd);
                if (_bottom != 0)
                    ReEmbed(gd, _bottom);
                // GigDataOffFix
                _bottom = GigElById(gd.id).GetOffset().Top + gd.dim.height + Css.OffsetVert(gd.css, Gig.DeefCss["padding-top"]);
            }
        }

        private void ReEmbed(GigData gd, int bottom)
        {
            // GigDataOffFix
            int s = GigElById(gd.id).GetOffset().Top;
            int n = bottom + Script.Value(CurrentSlide.embedGap, 10);
            if (s != n)
            {
                // GigDataOffFix
                //gd.off.Top = n;
                //gd.pos.Top -= s - n;
                jQuery.Select("#" + gd.id).CSS(new Dictionary("top", gd.pos.Top, "left", gd.pos.Left));
            }
        }

        public void ReSizeEv(jQueryEvent e)
        {
            DomElement.CancelEvent(e);
            foreach (KeyValuePair<string, GigData> gig in CurrentSlide.gigs)
            {
                AutoSize(gig.Value);
            }
        }

        private int SortTops(object a, object b)
        {
            // GigDataOffFix - used to use off instead of pos
            return ((GigData)a).pos.Top > ((GigData)b).pos.Top ? 1 : -1;
        }

        public static void SaveSpinnerOnEv(jQueryEvent e)
        {
            Window.SetTimeout(
                delegate
                {
                    jQuery.Select("#GearsBtnOuter").AddClass("GearsGrinding");
                    jQuery.Select("#GearsBtn").Attribute("src", Uri.Img("Common/spinner16.gif"));
                }, 1);
        }

        public static void SaveSpinnerOffEv(jQueryEvent e)
        {
            Window.SetTimeout(
                delegate
                {
                    jQuery.Select("#GearsBtnOuter").RemoveClass("GearsGrinding");
                    jQuery.Select("#GearsBtn").Attribute("src", Uri.Img("Common/gear16.gif"));
                }, 300);
        }

        public static void DropListen(jQueryObject el)
        {
            el
                .Unbind("drop.Gig")
                .Bind("drop.Gig", GseInstance.CreateDropItemEv)
                .Unbind("dragover.Gig")
                .Bind("dragover.Gig", DropTarget.DragOverEv)
                ;
        }

        public void Listen()
        {
            DropListen(jQuery.Select("#PageMaster"));

            jQuery.Document
                .Unbind("keydown.Gig")
                .Bind("keydown.Gig",
                      delegate(jQueryEvent e)
                      {
                          jQueryObject t = jQuery.FromElement(e.Target);
                          if (t.Is("input, textarea, select"))
                              return;
                          //Inform.Debug("GigShowEdit.Listen Target={0} CurrentTarget={1}", e.Target.TagName, e.CurrentTarget != null ? e.CurrentTarget.TagName : "null");
                          if (e.Which != 16 /*shift*/)
                              ClearPaint();
                          if (ThemeYoshi.SupressCurateEvents || !ThemeYoshi.PageDone) return;
                          if (e.Which == 66 /*b*/)
                              ToBackEv(e);
                              //else if (e.Which == 65 /*a*/&& !e.ShiftKey)
                              //    FocGigEdit.GroupAddEv(e);
                          else if (e.Which == 67 /*c*/&& e.CtrlKey)
                              CopyEv(e);
                          else if (e.Which == 46 /*del*/|| e.Which == 68 /*d*/ || e.Which == 8 /*bs*/)
                              DeleteEv(e);
                          else if (e.Which == 69 /*e*/&& !e.ShiftKey && e.CtrlKey)
                              EditElCssEv(e);
                          else if (e.Which == 69 /*e*/&& e.ShiftKey)
                              EditRawGigs.Ev(e, this);
                          else if (e.Which == 70 /*f*/&& !e.CtrlKey && !e.AltKey)
                              ToFrontEv(e);
                          else if (e.Which == 70 /*f*/&& e.CtrlKey && e.AltKey)
                              FixEv(e);
                          else if (e.Which == 71 && !e.ShiftKey /*g*/)
                              FocGigEdit.GroupifyEv(e);
                          else if (e.Which == 71 && e.ShiftKey /*g*/)
                              FocGigEdit.UngroupEv(e);
                          else if (e.Which == 72 /*h*/)
                              ToHigherEv(e);
                          else if (e.Which == 76 /*l*/)
                              ToLowerEv(e);
                          else if (e.Which == 80 /*p*/&& !e.CtrlKey)
                              Pages.Ev(e);
                              //else if (e.Which == 82 /*r*/)
                              //    FocGigEdit.GroupDropEv(e);
                              //else if (e.Which == 83 && !e.ShiftKey /*s*/)
                              //    FocGigEdit.GroupStartEv(e);
                          else if (e.Which == 84 /*t*/)
                              FocGigEdit.GearsOnEv(e);
                          else if (e.Which == 86 /*v*/&& e.CtrlKey)
                              PasteEv(e);
                          else if (e.Which == 90 /*z*/&& !e.CtrlKey && !e.AltKey)
                              FocGigEdit.ZeroEv(e);
                          else if (e.Which == 90 /*z*/&& e.CtrlKey && !e.AltKey)
                              UndoEv(e);
                          else if (e.Which == 90 /*z*/&& e.CtrlKey && e.AltKey)
                              RedoEv(e);
                          else if (e.Which == 39 /*right*/)
                              NextPageEv(e);
                          else if (e.Which == 37 /*left*/)
                              PriorPageEv(e);
                      }
                )
                ;
        }

        public void Muffel()
        {
            jQuery.Select("body")
                .Unbind("dragover.Gig")
                .Unbind("drop.Gig")
                ;
            jQuery.Document.Unbind("keydown.Gig");
        }

        [AlternateSignature]
        public extern void UpdateGigAw(Await awp, Action<object> fnModify);

        public void UpdateGigAw(Await awp, Action<object> fnModify, Action fnComplete)
        {
            FileManager.UpdateFileAw(awp, PartitionKey, RowKey, fnModify, fnComplete);
        }

        public void AddNewBg()
        {
            new Await()
                .AddDl(
                    delegate(Await aw)
                    {
                        UpdateGigAw(aw,
                                    delegate(object slidesD)
                                    {
                                        SlideData gigs = ((List<SlideData>) slidesD)[0];
                                        gigs.hasBackground = true;
                                        Uri.BumpVersion();
                                    });
                    })
                .AddAw(new Ask().FileUploadAw, new Dictionary("Msg", "Upload new background image", "path", Uri.Join("pg/gigs/", ThemeYoshi.CurrentPageName), "filename", BgImgName, "password", (string) Storage.GetLocal(ThemeYoshi.CuratePasswordKey)))
                .AddAw(DomElement.RequireResultAw, DomElement.NotFailOrEmpty)
                .Commit();
        }

        public void ColorizeBg()
        {
            ModOptions mo = new ModOptions();
            mo.IsPgBackground = true;
            mo.Prop = "background-color";
            mo.ToChange = jQuery.Select("body");
            mo.ToStore = CurrentSlide.css;
            mo.Title = "page background color";
            mo.OnChange = StdCssSet;
            mo.OnSave = delegate { SaveAllAw(Await.AsyncAw); };
            new Colorizer(mo);
        }

        public void StdCssSet(ModOptions opts, object v)
        {
            string cc = (string) v;
            opts.ToChange.CSS(opts.Prop, cc);
            opts.ToStore[opts.Prop] = cc;
            if (opts.IsPgBackground)
                SetBackgroundColor(cc);
        }

        public static void FixEv(jQueryEvent e)
        {
            DomElement.CancelEvent(e);
            Await awx = new Await();
            foreach (KeyValuePair<string, PageD> pk in ThemeYoshi.Pages.pages)
            {
                awx.AddAw(FixClosure(pk.Value));
            }
            awx.Commit();
            Uri.BumpVersion();
            //ThemeBase.ReloadPage();
        }

        private static Action<Await> FixClosure(PageD pg)
        {
            return delegate(Await awp)
                   {
                       FileManager.UpdateFileAw(awp, Uri.Join("pg/gigs", pg.Name), "curator",
                                                delegate(object slidesD)
                                                {
                                                    List<SlideData> slides = (List<SlideData>) slidesD;

                                                    foreach (SlideData slide in slides)
                                                    {
                                                        slide.css = (Dictionary) jQuery.Extend(new Dictionary(), ThemeYoshi.DefaultPageCss, slide.css);

                                                        Dictionary<string, GigData> gigs = slide.gigs;
                                                        foreach (KeyValuePair<string, GigData> gp in gigs)
                                                        {
                                                            GigData gd = gp.Value;
                                                            Inform.Debug("Fixing page {0}, slide {1}, type {2}", pg.Name, slide.slide, gd.type);
                                                            switch (gd.type)
                                                            {
                                                                case "DropImage":
                                                                    FixAddClass(gd, "img", Gig.GigContentClassName);
                                                                    break;
                                                                case "TextItem":
                                                                    gd.html = gd.html.ReplaceRegex((RegularExpression) Script.Literal(@"/<p[^>]*?>/g"), "<div class='GigC'>").Replace("</p>", "</div>");
                                                                    //Inform.Debug("New html = {0}", gd.html);
                                                                    FixAddClass(gd, Gig.DotGigContentClassName, Gig.GigContentClassName);
                                                                    break;
                                                                case "TextBox":
                                                                    FixAddClass(gd, "input[type=text]", Gig.GigContentClassName);
                                                                    break;
                                                                case "TextArea":
                                                                    FixAddClass(gd, "textarea", Gig.GigContentClassName);
                                                                    break;
                                                                case "Button":
                                                                    FixAddClass(gd, "button", Gig.GigContentClassName);
                                                                    break;
                                                                case "EmbeddedCode":
                                                                    //Inform.Debug("############ Manually fix EmbeddedCode in {0} ############", Uri.Join(pg.Name, "curator.txt"));
                                                                    break;
                                                                case "ClassItem":
                                                                    Inform.Debug("We're good.");
                                                                    break;
                                                                default:
                                                                    {
                                                                    }
                                                                    break;
                                                            }
                                                        }
                                                    }
                                                }
                           );
                   };
        }

        private static void FixAddClass(GigData gd, string selector, string className)
        {
            //string html = gd.html.ReplaceRegex((RegularExpression)Script.Literal(@"/\s*(style|title)=(""[^""]*?""|'[^']*?')/g"), "");
            jQueryObject cel = GigEdit.DomifyContent(gd.html, selector);
            gd.html = FixRepContent(gd.html, DomElement.OuterHtml(cel.AddClass(className)), selector);
            cel.Remove();
            Inform.Debug("New html = {0}", gd.html);
        }

        private static string FixRepContent(string html, string content, string selector)
        {
            selector = selector ?? Gig.DotGigContentClassName;
            jQueryObject el = jQuery.FromHtml(html).AppendTo(Document.Body);
            el.Find(selector).Parent().Plugin<Jewl>().SetContent(content);
            // Remove style from div's
            jQueryObject fx = jQuery.Select("div", el);
            if (el.Is("div"))
                fx.Add(el[0]);
            fx.RemoveAttr("style");
            string ot = DomElement.OuterHtml(el);
            el.Remove();
            return ot;
        }

        #region Finders

        public bool HasFocus
        {
            get
            {
                try
                {
                    return FocGigEdit.Element.Find("div.Sizer").Is(":visible") || jQuery.Select("div.GigMu").Is(":visible");
                }
                catch
                {
                    return false;
                }
            }
        }

        public GigEdit FocGigEdit
        {
            get { return _focused ?? (GigEdit) Exceptions.NullReferenceException; }
            set
            {
                _focused = value;
                //Script.Literal("window.focus()");
                DomElement.Unfocus();
            }
        }

        public GigData FocusedGd
        {
            get { return Slides[Slide].gigs[FocGigEdit.Element.GetAttribute("id")] ?? (GigData) Exceptions.NullReferenceException; }
        }

        public static GigEdit GigEditById(string id)
        {
            try
            {
                return (GigEdit) GigElById(id).GetDataValue("gigEdit");
            }
            catch
            {
                return null;
            }
        }

        public static GigEdit GigEditByEl(jQueryObject el)
        {
            try
            {
                return (GigEdit) el.GetDataValue("gigEdit");
            }
            catch
            {
                return null;
            }
        }

        public jQueryObject El()
        {
            return FocGigEdit.Element;
        }

        #endregion Finders

        #region Events

        private readonly jQueryObject _snapToNone = (jQueryObject) Script.Literal("jQuery()");
        private List<GroupItem> _group;
        private Dictionary<string, string> _reIds;
        private jQueryObject _snapToEl;
        private jQueryObject _snapToReal;
        private double _snapToH;
        private double[] _snapToPos;
        private double _snapToW;
        private int _stX;
        private int _stY;

        private readonly Dictionary _dragOptions;

        //public Dictionary DragOptions
        //{
        //    get
        //    {
        //        return new Dictionary(
        //            "start", (Action<jQueryEvent, jQueryUiObject>) DragStart,
        //            "drag", (Action<jQueryEvent, jQueryUiObject>) DragDrag,
        //            "stop", (Action<jQueryEvent, jQueryUiObject>) DragStop,
        //            "helper", "clone",
        //            "appendTo", PageSelectToEl
        //            );
        //    }
        //}

        public void MakeDraggable(Gig gg)
        {
            gg.Draggable(_dragOptions, delegate { });
        }

        public void SetGigEv(jQueryEvent e, string prop, object value)
        {
            if (BailNoFocusEv(e)) return;
            ForGroup(e,
                     delegate(GroupItem gi)
                     {
                         if (value != null)
                             Type.SetField(gi.Gd, prop, value);
                         else
                             Type.DeleteField(gi.Gd, prop);
                     });
            ReSlide(e);
        }

        public bool BailNoFocusEv(jQueryEvent e)
        {
            if (!HasFocus)
            {
                DomElement.CancelEvent(e);
                Cluster.FlashWindow();
                return true;
            }
            return false;
        }

        public void CancelAndSaveAll(jQueryEvent e)
        {
            SaveAllAw(Await.AsyncAw);
            DomElement.CancelEvent(e);
        }

        public void ReSlideAndSave(jQueryEvent e)
        {
            DomElement.CancelEvent(e);
            ReEmbedEv(e);
            ReSizeEv(e);
            new Await()
                .AddAw(RestoreSlideAw, Slide)
                .AddDx(delegate { CancelAndSaveAll(e); })
                .Commit();
        }

        public void ReSlide(jQueryEvent e)
        {
            DomElement.CancelEvent(e);
            ReEmbedEv(e);
            ReSizeEv(e);
            RestoreSlideAw(Await.AsyncAw, Slide);
        }

        public void CreateDropItemEv(jQueryEvent e)
        {
            // Fix drop event bug in jQuery
            if (Type.GetScriptType(e.PageX) != "number")
            {
                Script.Literal("{0}.pageX = {0}.originalEvent.pageX || {0}.originalEvent.clientX", e);
                Script.Literal("{0}.pageY = {0}.originalEvent.pageY || {0}.originalEvent.clientY", e);
            }
            Dictionary drop = DropTarget.DropObject(e);
            if (drop["url"] != null)
                ImageItem((string) drop["url"], e.PageX, e.PageY);
        }

        public void ToFrontEv(jQueryEvent e)
        {
            if (BailNoFocusEv(e)) return;
            ForGroup(e, delegate(GroupItem gi) { gi.El.AppendTo(gi.El.Parent()); });
            CancelAndSaveAll(e);
        }

        public void ToBackEv(jQueryEvent e)
        {
            if (BailNoFocusEv(e)) return;
            ForGroup(e, delegate(GroupItem gi) { gi.El.PrependTo(gi.El.Parent()); });
            CancelAndSaveAll(e);
        }

        public void ToHigherEv(jQueryEvent e)
        {
            if (BailNoFocusEv(e)) return;
            ForGroup(e, delegate(GroupItem gi) { gi.El.Next().InsertBefore(gi.El); });
            CancelAndSaveAll(e);
        }

        public void ToLowerEv(jQueryEvent e)
        {
            if (BailNoFocusEv(e)) return;
            ForGroup(e, delegate(GroupItem gi) { gi.El.Prev().InsertAfter(gi.El); });
            CancelAndSaveAll(e);
        }

        public void FloatEv(jQueryEvent e)
        {
            SetGigEv(e, "floated", true);
        }

        public void UnFloatEv(jQueryEvent e)
        {
            SetGigEv(e, "floated", false);
        }

        public void EmbeddedEv(jQueryEvent e)
        {
            SetGigEv(e, "embedded", true);
        }

        public void UnEmbeddedEv(jQueryEvent e)
        {
            SetGigEv(e, "embedded", false);
        }

        public void AutosizeEv(jQueryEvent e)
        {
            SetGigEv(e, "autosize", true);
        }

        public void UnAutosizeEv(jQueryEvent e)
        {
            SetGigEv(e, "autosize", false);
        }

        public void FormatUlEv(jQueryEvent e)
        {
            SetGigEv(e, "listType", "ul");
        }

        public void FormatOlEv(jQueryEvent e)
        {
            SetGigEv(e, "listType", "ol");
        }

        public void FormatPlainEv(jQueryEvent e)
        {
            SetGigEv(e, "listType", null);
        }

        public void NextPageEv(jQueryEvent e)
        {
            DomElement.CancelEvent(e);
            IReadonlyCollection<string> pp = ThemeYoshi.Pages.pages.Keys;
            int i = 0;
            foreach (string s in pp)
            {
                if (s == ThemeYoshi.CurrentPageName)
                    break;
                i++;
            }
            string nxt = i == pp.Count - 1 ? pp[0] : pp[i + 1];
            ThemeYoshi.GoPage(ThemeYoshi.CurrentPageClass, nxt);
        }

        public void PriorPageEv(jQueryEvent e)
        {
            DomElement.CancelEvent(e);
            IReadonlyCollection<string> pp = ThemeYoshi.Pages.pages.Keys;
            int i = 0;
            foreach (string s in pp)
            {
                if (s == ThemeYoshi.CurrentPageName)
                    break;
                i++;
            }
            string nxt = i <= 0 ? pp[pp.Count - 1] : pp[i - 1];
            ThemeYoshi.GoPage(ThemeYoshi.CurrentPageClass, nxt);
        }

        public void EditElCssEv(jQueryEvent e)
        {
            if (BailNoFocusEv(e)) return;
            string id = FocusedGd.id;
            new Await()
                .AddAw(new EditCss().EditDAw, FocusedGd)
                .AddAw(DomElement.RequireResultAw, DomElement.NotFail)
                .AddDl(
                    delegate(Await aw)
                    {
                        try
                        {
                            Dictionary d = (Dictionary) aw.Result;
                            SlideData sd = CurrentSlide;
                            // Could return the full gig or just the gig's css
                            if (Type.HasField(d, "css"))
                            {
                                sd.gigs[id] = (GigData) (object) d;
                            }
                            else
                            {
                                sd.gigs[id].css = d;
                            }
                            ReSlideAndSave(e);
                        }
                        catch (Exception ex)
                        {
                            Inform.Error("Exception updating gig css: {0}", ex);
                        }
                    }
                )
                .Commit();
        }

        public void DeleteEv(jQueryEvent e)
        {
            if (BailNoFocusEv(e)) return;
            Await awx = new Await();
            if (!e.AltKey)
                awx
                    .AddAw(new Ask().OkCancelAw, ThemeYoshi.ForSure)
                    .AddAw(DomElement.RequireResultAw, true)
                    ;
            awx
                .AddDx(
                    delegate
                    {
                        ForCluster(e,
                                 delegate(GroupItem gi)
                                 {
                                     GigElById(gi.Gd.id).Remove();
                                     CurrentSlide.gigs.Remove(gi.Gd.id);
                                 });
                        ReSlideAndSave(e);
                        Cluster.GlassOff();
                    }
                )
                .Commit();
            DomElement.CancelEvent(e);
        }

        //public void ChompWindowEv(jQueryEvent e)
        //{
        //    jQueryObject f = jQuery.Select(".ChompIFrame");
        //    if (f.Length == 0)
        //    {
        //        new ChompWindow().Src("http://www.google.com").InsertAfter(PageSelectTo);
        //    }
        //    else
        //    {
        //        f.Remove();
        //    }
        //}

        public void CopyEv(jQueryEvent e)
        {
            if (BailNoFocusEv(e)) return;
            List<GigData> items = new List<GigData>();
            ForCluster(e, delegate(GroupItem gi) { items.Add(gi.Gd); });
            Storage.SetLocal("CopyBuffer", items);
            Cluster.GlassOff();
            RedrawPageGear();
            DomElement.CancelEvent(e);
        }

        public void PasteEv(jQueryEvent e)
        {
            List<GigData> gdl = (List<GigData>) Storage.GetLocal("CopyBuffer");
            if (gdl != null)
            {
                _reIds = new Dictionary(); // Reset the id dictionary
                if (!jQuery.IsArray(gdl))
                    gdl = (List<GigData>) Script.Literal("[{0}]", gdl);
                Await awx = new Await();
                if (gdl.Count == 1)
                {
                    GigData gd = gdl[0];
                    gd.groupId = null;
                    gd.parentId = null;
                    gd.clusterId = null;
                }
                foreach (GigData item in gdl)
                {
                    PasteClosure(awx, item);
                }
                awx.AddDx(
                    delegate
                    {
                        Storage.SetLocal("CopyBuffer", gdl); // Update copy buffer with id changes
                        ReSlideAndSave(e);
                        jQuery.Select(PageCssTo).FadeIn(300); // Why? Where was it hidden before this?
                    });
                awx.Commit();
            }
        }

        private void PasteClosure(Await awp, GigData item)
        {
            awp.AddDl(delegate(Await aw) { PasteAw(aw, item); });
        }

        public void PasteAw(Await awp, GigData item)
        {
            if (jQuery.Select("#" + item.id).Length > 0)
            {
                // If item came from page then shift it down before pasting
                item.pos.Top += 20;
                // GigDataOffFix
                //item.off.Top += 20;
            }
            item.id = ReId(item.id);
            if (item.parentId != null)
                item.parentId = ReId(item.parentId);
            if (item.groupId != null)
                item.groupId = ReId(item.groupId);
            if (item.clusterId != null)
                item.clusterId = ReId(item.clusterId);
            Gig gg = InjectIntoPage(item, new GigEdit(this));
            if (HasFocus)
            {
                Insert(gg, FocGigEdit);
                GigData gd = FocusedGd;
                if (gd.actions.paste != null)
                {
                    try
                    {
                        Action<Gig, GigData> fn = (Action<Gig, GigData>) Script.Eval(gd.actions.paste);
                        fn(gg, gd);
                    }
                    catch (Exception ex)
                    {
                        Inform.Error("Failed paste action: {0}", ex);
                    }
                }
            }
            awp.Done();
        }

        private string ReId(string id)
        {
            if (!Type.HasField(_reIds, id))
                _reIds[id] = Formulas.NewId;
            return _reIds[id];
        }

        public void CopySlideEv(jQueryEvent e)
        {
            List<GigData> items = new List<GigData>();
            foreach (KeyValuePair<string, GigData> p in CurrentSlide.gigs)
            {
                items.Add(p.Value);
            }
            Storage.SetLocal("CopyBuffer", items);
            new Await().AddFn(RedrawPageGear).Commit();
        }

        public void Insert(Gig before, Gig after)
        {
            before.Element.InsertBefore(after.Element);
            Resort();
        }

        private void DragStart(jQueryEvent e, jQueryUiObject ui)
        {
            ThemeYoshi.SupressCurateEvents = true;
            ClearPaint();
            FocGigEdit.MaskOff();
            GigElBySelect(".GigMe").RemoveClass("GigMe");
            ForCluster(e, delegate(GroupItem gi) { gi.El.AddClass("GigMe"); });
            if (_group.Count <= 1)
                _group.Clear();
            jQueryObject el = jQuery.FromElement(e.Target).Closest(Gig.DotGigClassName);
            _snapToReal = el;
            el = ui.helper;
            if (el.Length > 0)
            {
                _stX = el.GetOffset().Left;
                _stY = el.GetOffset().Top;
                _snapToEl = el;
                _snapToW = el.GetOuterWidth();
                _snapToH = el.GetOuterHeight();
                double tilt = Math.Abs((double) Type.GetField(el[0], "tilt"));
                if (tilt > 0)
                {
                    double ww = _snapToW;
                    double hh = _snapToH;
                    double tt = el.Plugin<SnapTo>().SnapToTilt(tilt);
                    _snapToW = el.Plugin<SnapTo>().SnapToTiltW(tt, ww, hh);
                    _snapToH = el.Plugin<SnapTo>().SnapToTiltH(tt, ww, hh);
                }
                el.AddClass("GigMe");
                _snapToReal.AddClass("GigMe");
                el.Plugin<SnapTo>().SnapCollect("div.Gig:not(.GigMe), " + PageSelectTo);
                if ((string)_dragOptions["helper"] == "clone")
                    _snapToReal.Hide();
            }
        }

        private void DragDrag(jQueryEvent e, jQueryUiObject ui)
        {
            if (_snapToEl.Length > 0)
            {
                jQueryPosition p = _snapToEl.GetOffset();
                if (e.AltKey)
                    _snapToPos = new double[] {p.Left, p.Top};
                else
                    _snapToPos = _snapToEl.Plugin<SnapTo>().SnapPos(p.Left, p.Top, _snapToW, _snapToH);

                if (_group.Count > 0)
                {
                    foreach (GroupItem gi in _group)
                    {
                        gi.El.CSS(new Dictionary("top", gi.Pos.Top + p.Top - _stY, "left", gi.Pos.Left + p.Left - _stX));
                    }
                }
            }
        }

        private void DragStop(jQueryEvent e, jQueryUiObject ui)
        {
            ThemeYoshi.SupressCurateEvents = false;
            if (_snapToEl.Length > 0)
            {
                _snapToReal.Show();
                jQueryPosition p = _snapToEl.GetOffset();
                //Inform.Debug("DragStop e.AltKey={0}", e.AltKey);
                if (!e.AltKey)
                {
                    _snapToPos = _snapToEl.Plugin<SnapTo>().SnapPos(p.Left, p.Top, _snapToW, _snapToH);
                    //_snapToEl.Offset((jQueryPosition) (object) new Dictionary("top", _snapToPos[1], "left", _snapToPos[0]));
                    _snapToReal.Offset((jQueryPosition)(object)new Dictionary("top", _snapToPos[1], "left", _snapToPos[0]));
                }
                _snapToEl.Plugin<SnapTo>().SnapDump();
                _snapToEl = _snapToNone;
                //Inform.Debug("DragStop {0}, {1}", _snapToPos[0], _snapToPos[1]);

                if (_group.Count > 0)
                {
                    double dx = _snapToPos[0] - _stX;
                    double dy = _snapToPos[1] - _stY;
                    foreach (GroupItem gx in _group)
                    {
                        GigData gd = gx.Gd;
                        gd.pos.Top = (int)(gx.Pos.Top + dy);
                        gd.pos.Left = (int)(gx.Pos.Left + dx);
                        // GigDataOffFix
                        //gd.off.Top = gx.Off.Top + dy;
                        //gd.off.Left = gx.Off.Left + dx;
                        gx.El.CSS(new Dictionary("top", gd.pos.Top, "left", gd.pos.Left));
                    }
                    _group.Clear();
                }
                if ((string)_dragOptions["helper"] == "clone")
                    _snapToEl.Remove();
            }
            //Inform.Debug("DragStop1");
            jQuery.Document.Trigger(AfterDragName);
            jQuery.Document.Bind("AfterDrag2",
                                 delegate
                                 {
                                     //Inform.Debug("DragStop2");
                                     jQuery.Document.Unbind("AfterDrag2");
                                     GigElBySelect(".GigMe").RemoveClass("GigMe");
                                     FocGigEdit.ScoopPosition();
                                     Reorder();
                                     FocGigEdit.MaskOn();
                                     ReSlideAndSave(e);
                                 });
            jQuery.Document.Trigger("AfterDrag2");
            jQuery.Select("." + MaskClassName, Document.Body).Remove();
        }

        #endregion Events
    }

    public sealed class GroupItem
    {
        public jQueryObject El;
        public GigData Gd;
        // GigDataOffFix
        //public Pos Off;
        public Pos Pos;
        public jQueryEvent e;

        public GroupItem FillFromGd(jQueryEvent ev, GigData gd)
        {
            El = GigShow.GigElById(gd.id);
            Gd = gd;
            Pos = (Pos) jQuery.Extend(new Dictionary(), gd.pos);
            // GigDataOffFix
            //Off = (Pos) jQuery.Extend(new Dictionary(), gd.off);
            e = ev;
            return this;
        }
    }

    public sealed class UndoItem
    {
        public string Buffer;
        public int Slide;

        public UndoItem(string buffer, int slide)
        {
            Buffer = buffer;
            Slide = slide;
        }

        public static List<SlideData> Parse(string content)
        {
            try
            {
                return (List<SlideData>) Json.Parse(content);
            }
            catch
            {
                return null;
            }
        }
    }
}